package com.atguigu.gmall.wms.service.impl;

import com.atguigu.gmall.wms.vo.SkuLockVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.gson.Gson;
import io.lettuce.core.RedisClient;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.gmall.common.bean.PageResultVo;
import com.atguigu.gmall.common.bean.PageParamVo;

import com.atguigu.gmall.wms.mapper.WareSkuMapper;
import com.atguigu.gmall.wms.entity.WareSkuEntity;
import com.atguigu.gmall.wms.service.WareSkuService;
import org.springframework.util.CollectionUtils;


@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuMapper, WareSkuEntity> implements WareSkuService {

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RedisTemplate redisTemplate;

    String localKeyPrefix = "stock:lock";
    String skuLockVosKeyPrefix = "sku:locks";

    Gson gson = new Gson();

    @Override
    public PageResultVo queryPage(PageParamVo paramVo) {
        IPage<WareSkuEntity> page = this.page(
                paramVo.getPage(),
                new QueryWrapper<WareSkuEntity>()
        );

        return new PageResultVo(page);
    }

    @Override
    public List<WareSkuEntity> querySkuWare(Long skuId) {
        LambdaQueryWrapper<WareSkuEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(WareSkuEntity::getSkuId, skuId);
        return this.list(wrapper);
    }


    /**
     * 整体逻辑：先校验，将所有商品项分为两种，够和不够，够的锁定库存，不够的不锁定
     * 如果存在 整体存在不够的情况，
     *
     * @param skuLockVos
     * @return
     */
    @Override
    public List<SkuLockVo> checkAndStock(List<SkuLockVo> skuLockVos) {
        /*
          遍历list ,每个skuLockVos中，根据skuId和count查询库存，
          如果成功，设置stock=true。过滤stock=true
          锁库存（就是更新一下wms.stock_locked字段的属性值，简单）
          返回新的list集合
          注意：验库存和锁库存为了避免其他的请求插队(两个请求同时操作同一组数据)，导致得到的结果不准确，需要使用分布式锁保证原子性(一起成功或者一起失败)
                --加锁时，可以使用skuId拼接锁的key，相同的skuId的多个锁库存操作才会互斥。
                          不可以使用一个统一的key去加锁，因为这样，别的请求只能等我这个请求执行完了才能继续执行

         */

        //非空校验
        if (CollectionUtils.isEmpty(skuLockVos)) {
            return null;
        }

        //验库存并锁库存
        for (SkuLockVo skuLockVo : skuLockVos) {
            checkStock(skuLockVo);
        }

        //锁库存，流操作直接写完，返回锁库存失败（即库存不足）的List<SkuLockVo>
        List<SkuLockVo> failSkuLockVos = skuLockVos.stream().filter((skuLockVo) -> !skuLockVo.getStock()).collect(Collectors.toList());//当filter中为true是，该元素会被保留

        if (!CollectionUtils.isEmpty(failSkuLockVos)) {//如果有个别商品库存不足，那么整体返回最初的状态，把之前商品足够的、库存被锁定的也解锁
            //解锁已经那些足够商品的库存，回到最初的状态
            skuLockVos.stream().filter((skuLockVo -> skuLockVo.getStock())).forEach(skuLockVo -> {//直接对流对象进行forEach
                baseMapper.unlockedStock(skuLockVo.getWareSkuId(), skuLockVo.getCount());
            });
            //返回失败的库存
            return failSkuLockVos;
        }

        //无失败库存，返回空
        //拿到订单的List<SkuLockVo>,以订单号orderToken为key,存入redis,方便以后订单失败，整体解锁库存
        redisTemplate.opsForValue().set(skuLockVosKeyPrefix + skuLockVos.get(0).getOrderToken(),gson.toJson(skuLockVos));

        return null;

    }


    /**
     * 验库存操作：保证原子性，不能被插队，不能被中断。
     * 使用分布式锁，因为以后我们可能多实例的方式来启动，如果我们加的是jvm级别的锁，只能以单实例的方式来启动了，多实例就失效了。
     * 而且我们使用分布式的公平锁：举例，A请求在执行验库存时，它拿到了这个锁，这时候有B请求和C请求也进来了，B和C谁拿到锁呢？看谁先来谁拿到，这样公平
     */
    private void checkStock(SkuLockVo skuLockVo) {
        Long skuId = skuLockVo.getSkuId();
        Integer count = skuLockVo.getCount();

        //每个skuId都要自己的分布式锁，这意味着，多个用户提交订单时，每个用户的skuId如果不同，那么他们之间就没有互斥性
        RLock fairLock = redissonClient.getFairLock(localKeyPrefix + skuId);
        try {
            fairLock.lock();//加分布式锁
            List<WareSkuEntity> wareSkuEntities = baseMapper.checkStock(skuId, count);
            if (CollectionUtils.isEmpty(wareSkuEntities)) {
                skuLockVo.setStock(false);//该s商品库存不足
                return;
            }
            //有库存 锁库存
            //锁库存查找查库时，会根据订单的收货地址匹配城市的id，匹配合适的仓库id，我们这里不写了，直接使用第一个仓库
            WareSkuEntity wareSkuEntity = wareSkuEntities.get(0);
            boolean flag = baseMapper.lockStock(wareSkuEntity.getId(), count);
            if (flag) {//锁库存成功
                skuLockVo.setStock(true);
            } else {//锁库存失败
                skuLockVo.setStock(false);
            }
        } finally {
            fairLock.unlock();//释放分布式锁
        }

    }


}